use moss::from_meta::FromMeta;
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::{LitStr, Meta};
use validator_expand::validator_derive::{NameAttr, ValidateAttr};

pub struct Attrs {
    pub docs: TokenStream,
    pub attrs: TokenStream,
    pub serde: TokenStream,
    pub constraits: TokenStream,
    pub name: TokenStream,
}

#[derive(Default)]
pub struct DocsAttr {
    pub doc_count: i32,
    pub docs_content: String,
}

impl DocsAttr {
    pub fn visit_attr(&mut self, attr: &syn::Attribute) -> bool {
        if let Meta::NameValue(meta) = &attr.meta {
            if meta.path.is_ident("doc") {
                let content = &meta.value;
                if let syn::Expr::Lit(lit) = content {
                    if let syn::Lit::Str(lit) = &lit.lit {
                        let content = lit.value();
                        let content = content.trim_start_matches(' ');
                        self.docs_content.push_str(content);
                        self.docs_content.push('\n');
                        self.doc_count += 1;
                        return true;
                    }
                }
            }
        }
        false
    }

    pub fn to_token_stream(&self, into: bool) -> TokenStream {
        let mut docs_token = quote!(None);
        if self.doc_count != 0 {
            let docs_content = self.docs_content.trim_end_matches('\n').to_string();
            if into {
                docs_token = quote! {Some(#docs_content.into())};
            } else {
                docs_token = quote! {Some(#docs_content)};
            }
        }
        docs_token
    }
}

#[derive(FromMeta)]
#[name("serde")]
pub enum ContainerSerdeAttr {
    Rename {
        serialize: Option<LitStr>,
        deserialize: Option<LitStr>,
    },

    #[strip]
    RenameAll { rename_all: Option<LitStr> },
}

#[derive(FromMeta)]
#[name("serde")]
pub enum VariantSerdeAttr {
    Rename {
        serialize: Option<LitStr>,
        deserialize: Option<LitStr>,
    },

    #[strip]
    RenameAll { rename_all: Option<LitStr> },
}

#[derive(FromMeta)]
#[name("serde")]
pub enum FieldSerdeAttr {
    Rename {
        serialize: Option<LitStr>,
        deserialize: Option<LitStr>,
    },

    Default,

    Skip,

    SkipSerialize,

    SkipDeserialize,
}

pub enum AttrType {
    Container,
    Variant,
    Field,
    Other,
}

#[derive(Default)]
pub struct ContainerSerdeConfig {
    pub rename_serialize: Option<LitStr>,
    pub rename_deserialize: Option<LitStr>,
    pub rename_all: Option<LitStr>,
}

impl ContainerSerdeConfig {
    pub fn to_token_stream(&self) -> TokenStream {
        let rename_serialize = self
            .rename_serialize
            .as_ref()
            .map(|s| quote!(Some(#s.into())))
            .unwrap_or(quote!(None));

        let rename_deserialize = self
            .rename_deserialize
            .as_ref()
            .map(|s| quote!(Some(#s.into())))
            .unwrap_or(quote!(None));

        let rename_all = self
            .rename_all
            .as_ref()
            .map(|s| quote!(Some(#s.into())))
            .unwrap_or(quote!(None));

        quote! {
            glass::ContainerSerde {
                rename_serialize: #rename_serialize,
                rename_deserialize: #rename_deserialize,
                rename_all: #rename_all,
            }
        }
    }
}

#[derive(Default)]
pub struct VariantSerdeConfig {
    pub rename_serialize: Option<LitStr>,
    pub rename_deserialize: Option<LitStr>,
    pub rename_all: Option<LitStr>,
}

impl VariantSerdeConfig {
    pub fn to_token_stream(&self) -> TokenStream {
        let rename_serialize = self
            .rename_serialize
            .as_ref()
            .map(|s| quote!(Some(#s.into())))
            .unwrap_or(quote!(None));

        let rename_deserialize = self
            .rename_deserialize
            .as_ref()
            .map(|s| quote!(Some(#s.into())))
            .unwrap_or(quote!(None));

        let rename_all = self
            .rename_all
            .as_ref()
            .map(|s| quote!(Some(#s.into())))
            .unwrap_or(quote!(None));

        quote! {
            glass::VariantSerde {
                rename_serialize: #rename_serialize,
                rename_deserialize: #rename_deserialize,
                rename_all: #rename_all,
            }
        }
    }
}

#[derive(Default)]
pub struct FieldSerdeConfig {
    pub rename_serialize: Option<LitStr>,
    pub rename_deserialize: Option<LitStr>,
    pub default: bool,
    pub skip_serialize: bool,
    pub skip_deserialize: bool,
    pub skip: bool,
}

impl FieldSerdeConfig {
    pub fn to_token_stream(&self) -> TokenStream {
        let rename_serialize = self
            .rename_serialize
            .as_ref()
            .map(|s| quote!(Some(#s.into())))
            .unwrap_or(quote!(None));

        let rename_deserialize = self
            .rename_deserialize
            .as_ref()
            .map(|s| quote!(Some(#s.into())))
            .unwrap_or(quote!(None));

        let default = if self.default {
            quote!(true)
        } else {
            quote!(false)
        };

        let skip_serialize = if self.skip_serialize {
            quote!(true)
        } else {
            quote!(false)
        };

        let skip_deserialize = if self.skip_deserialize {
            quote!(true)
        } else {
            quote!(false)
        };

        let skip = if self.skip {
            quote!(true)
        } else {
            quote!(false)
        };

        quote! {
            glass::FieldSerde {
                rename_serialize: #rename_serialize,
                rename_deserialize: #rename_deserialize,
                default: #default,
                skip_serialize: #skip_serialize,
                skip_deserialize: #skip_deserialize,
                skip: #skip,
            }
        }
    }
}

pub fn attrs_tokens_(attrs: &[syn::Attribute], into: bool, attr_type: AttrType) -> Attrs {
    let mut attrs_token = quote!();
    let mut docs_attr = DocsAttr::default();
    let mut constraits_token = quote!();
    let mut name_token = quote!(None);

    let mut container_serde_config = ContainerSerdeConfig::default();
    let mut variant_serde_config = VariantSerdeConfig::default();
    let mut field_serde_config = FieldSerdeConfig::default();
    for attr in attrs {
        let matched = docs_attr.visit_attr(attr);
        if matched {
            continue;
        }
        let name_attr = NameAttr::from_meta(&attr.meta);
        if let Ok(name_attr) = name_attr {
            let name = name_attr.0.unwrap_or_default();
            name_token = quote!(Some(#name.to_string()));
            continue;
        }

        let validate_attr = ValidateAttr::from_meta(&attr.meta);
        if let Ok(validate_attr) = validate_attr {
            match validate_attr {
                ValidateAttr::Length(min, max, message) => {
                    let message_token = message
                        .map(|m| quote!(Some(#m.into())))
                        .unwrap_or(quote!(None));
                    constraits_token = quote! {
                        #constraits_token
                        glass::ConstraitType::Length(
                            #min,
                            #max,
                            #message_token,
                        ),
                    };
                }
                ValidateAttr::Range(min, max, message) => {
                    let message_token = message
                        .map(|m| quote!(Some(#m.into())))
                        .unwrap_or(quote!(None));
                    constraits_token = quote! {
                        #constraits_token
                        glass::ConstraitType::Range(
                            #min.to_string(),
                            #max.to_string(),
                            #message_token,
                        ),
                    };
                }
                ValidateAttr::Regex(regex, message) => {
                    let message_token = message
                        .map(|m| quote!(Some(#m.into())))
                        .unwrap_or(quote!(None));
                    constraits_token = quote! {
                        #constraits_token
                        glass::ConstraitType::Regex(
                            #regex.into(),
                            #message_token,
                        ),
                    };
                }
                ValidateAttr::Enumer(ty, message) => {
                    let message_token = message
                        .map(|m| quote!(Some(#m.into())))
                        .unwrap_or(quote!(None));
                    constraits_token = quote! {
                        #constraits_token
                        glass::ConstraitType::Enumer(
                            <#ty as glass::Reflect>::type_info,
                            #message_token,
                        ),
                    };
                }
                ValidateAttr::SelfValidate(message) => {
                    let message_token = message
                        .map(|m| quote!(Some(#m.into())))
                        .unwrap_or(quote!(None));
                    constraits_token = quote! {
                        #constraits_token
                        glass::ConstraitType::SelfValidate(
                            #message_token,
                        ),
                    };
                }
                ValidateAttr::OnlySelfValidate => {
                    let message_token = quote!(None);
                    constraits_token = quote! {
                        #constraits_token
                        glass::ConstraitType::SelfValidate(
                            #message_token,
                        ),
                    };
                }
                ValidateAttr::Constant(value, message) => {
                    let message_token = message
                        .map(|m| quote!(Some(#m.into())))
                        .unwrap_or(quote!(None));
                    constraits_token = quote! {
                        #constraits_token
                        glass::ConstraitType::Constant(
                            #value.to_string(),
                            #message_token,
                        ),
                    };
                }
            }
            continue;
        }
        match attr_type {
            AttrType::Container => {
                let attr = ContainerSerdeAttr::from_meta(&attr.meta);
                if let Ok(attr) = attr {
                    match attr {
                        ContainerSerdeAttr::Rename {
                            serialize,
                            deserialize,
                        } => {
                            container_serde_config.rename_serialize = serialize;
                            container_serde_config.rename_deserialize = deserialize;
                        }
                        ContainerSerdeAttr::RenameAll { rename_all } => {
                            container_serde_config.rename_all = rename_all;
                        }
                    }
                    continue;
                };
            }
            AttrType::Variant => {
                let attr = VariantSerdeAttr::from_meta(&attr.meta);
                if let Ok(attr) = attr {
                    match attr {
                        VariantSerdeAttr::Rename {
                            serialize,
                            deserialize,
                        } => {
                            variant_serde_config.rename_serialize = serialize;
                            variant_serde_config.rename_deserialize = deserialize;
                        }
                        VariantSerdeAttr::RenameAll { rename_all } => {
                            variant_serde_config.rename_all = rename_all;
                        }
                    }
                    continue;
                };
            }
            AttrType::Field => {
                let attr = FieldSerdeAttr::from_meta(&attr.meta);
                if let Ok(attr) = attr {
                    match attr {
                        FieldSerdeAttr::Rename {
                            serialize,
                            deserialize,
                        } => {
                            field_serde_config.rename_serialize = serialize;
                            field_serde_config.rename_deserialize = deserialize;
                        }
                        FieldSerdeAttr::Default => {
                            field_serde_config.default = true;
                        }
                        FieldSerdeAttr::Skip => {
                            field_serde_config.skip = true;
                        }
                        FieldSerdeAttr::SkipSerialize => {
                            field_serde_config.skip_serialize = true;
                        }
                        FieldSerdeAttr::SkipDeserialize => {
                            field_serde_config.skip_deserialize = true;
                        }
                    }
                    continue;
                };
            }
            _ => {}
        }

        let attr_string = attr.to_token_stream().to_string();
        if into {
            attrs_token = quote! {
                #attrs_token #attr_string.into(),
            };
        } else {
            attrs_token = quote! {
                #attrs_token #attr_string,
            };
        }
    }
    constraits_token = quote!(vec![#constraits_token]);
    let docs_token = docs_attr.to_token_stream(into);
    let mut serde_token = quote!();
    match attr_type {
        AttrType::Container => {
            serde_token = container_serde_config.to_token_stream();
        }
        AttrType::Variant => {
            serde_token = variant_serde_config.to_token_stream();
        }
        AttrType::Field => {
            serde_token = field_serde_config.to_token_stream();
        }
        _ => {}
    };
    Attrs {
        docs: docs_token,
        attrs: quote! {vec![#attrs_token]},
        constraits: constraits_token,
        serde: serde_token,
        name: name_token,
    }
}

pub fn attrs_tokens_string(attrs: &[syn::Attribute], attr_type: AttrType) -> Attrs {
    attrs_tokens_(attrs, true, attr_type)
}

pub fn attrs_tokens_str(attrs: &[syn::Attribute], attr_type: AttrType) -> Attrs {
    attrs_tokens_(attrs, false, attr_type)
}
